/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_surf.c: surface-related refresh code

#include "quakedef.h"

ID3D10Buffer *d3d_SpriteVertexes = NULL;
QSHADER d3d_SpriteShader;

struct spritepolyvert_t
{
	float xy[2];
	float st[2];
};

class CSpriteHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		d3d_SpriteShader.CreateShaders (IDR_SPRITESHADER, "SpriteVS", NULL, "SpritePS", INPUT_LAYOUT_SPRITE);
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_SpriteVertexes);
	}

	void OnModeChange (void)
	{
		// bind surfaces to slot 6 always so that it doesn't need to be checked and never needs to change
		UINT stride = sizeof (spritepolyvert_t);
		UINT offset = 0;

		d3d_Device->IASetVertexBuffers (BUFFER_SLOT_SPRITES, 1, &d3d_SpriteVertexes, &stride, &offset);
	}
} d3d_SpriteHandler;


void Sprite_AddFrameToBuffer (model_t *m, mspriteframe_t *frame, spritepolyvert_t *verts)
{
	// this works by deliberately overflowing xy so that the correct texcoords will also write into st
	Vector4Set (verts[0].xy, frame->left, frame->down, 0, 1);
	Vector4Set (verts[1].xy, frame->left, frame->up, 0, 0);
	Vector4Set (verts[2].xy, frame->right, frame->down, 1, 1);
	Vector4Set (verts[3].xy, frame->right, frame->up, 1, 0);
}


void Sprite_CreateBuffers (void)
{
	model_t	*m;
	spritepolyvert_t *verts = NULL;
	QSCRATCHLOCKER Lock ((void **) &verts, __FUNCTION__);
	D3D10_SUBRESOURCE_DATA srd = {verts, 0, 0};
	int NumBufferVerts = 0;

	for (int j = 1; j < MAX_MODELS; j++)
	{
		if (!(m = cl.model_precache[j])) continue;

		// don't crash on models with 0 surfaces attempting to create a vertex buffer
		if (m->type != mod_sprite) continue;

		msprite_t *psprite = m->spritehdr;
		mspriteframe_t *frame;
		mspritegroup_t *pspritegroup;

		// create a buffer set
		for (int i = 0; i < psprite->numframes; i++)
		{
			if (psprite->frames[i].type == SPR_SINGLE)
			{
				frame = psprite->frames[i].frameptr;
				Sprite_AddFrameToBuffer (m, frame, verts);
				frame->FirstVertex = NumBufferVerts;
				NumBufferVerts += 4;
				verts += 4;
			}
			else
			{
				pspritegroup = (mspritegroup_t *) psprite->frames[i].frameptr;

				for (int k = 0; k < pspritegroup->numframes; k++)
				{
					frame = pspritegroup->frames[k];
					Sprite_AddFrameToBuffer (m, frame, verts);
					frame->FirstVertex = NumBufferVerts;
					NumBufferVerts += 4;
					verts += 4;
				}
			}
		}
	}

	// and now create the buffer (some maps/mods may not use sprites)
	if (NumBufferVerts > 0)
	{
		// release any buffer that may already exist
		SAFE_RELEASE (d3d_SpriteVertexes);

		// create our new buffer
		D3D10_BUFFER_DESC vbDesc = {
			sizeof (spritepolyvert_t) * NumBufferVerts,
			D3D10_USAGE_IMMUTABLE,
			D3D10_BIND_VERTEX_BUFFER,
			0,
			0
		};

		d3d_Device->CreateBuffer (&vbDesc, &srd, &d3d_SpriteVertexes);

		// re-bind the buffer
		d3d_SpriteHandler.OnModeChange ();
	}
}


/*
=============================================================

  SPRITE MODELS

=============================================================
*/

/*
================
R_GetSpriteFrame
================
*/
mspriteframe_t *R_GetSpriteFrame (entity_t *e)
{
	msprite_t *psprite = e->model->spritehdr;
	int frame = e->frame;

	if ((frame >= psprite->numframes) || (frame < 0))
	{
		Con_Printf (PRINT_DEFAULT, "R_DrawSprite: no such frame %d\n", frame);
		frame = 0;
	}

	if (psprite->frames[frame].type == SPR_SINGLE)
		return psprite->frames[frame].frameptr;
	else
	{
		mspritegroup_t *pspritegroup = (mspritegroup_t *) psprite->frames[frame].frameptr;

		int i = Mod_GetAutoAnimation (
			pspritegroup->intervals,
			pspritegroup->numframes,
			e->syncbase
		);

		return pspritegroup->frames[i];
	}
}


/*
=================
R_DrawSpriteModel

=================
*/
void R_DrawSpriteModel (entity_t *e)
{
	mspriteframe_t *frame;
	msprite_t *psprite;

	// don't even bother culling, because it's just a single
	// polygon without a surface cache
	frame = R_GetSpriteFrame (e);
	psprite = e->model->spritehdr;

	e->localmatrix.Identity ();

	// build the billboarding matrix from the vectors and position
	if (psprite->type == SPR_ORIENTED)
	{
		// bullet marks on walls
		e->localmatrix.Billboard (e->origin, e->angles);
	}
	else
	{
		// normal sprite
		e->localmatrix.Billboard (e->origin, ref.ViewAngles);
	}

	// this wasn't done for sprites during setup so do it now
	e->entitymatrix.Load (ref.MVPMatrix);
	e->entitymatrix.Mult (e->localmatrix);

	R_SetupObjectConstants (e, ENTALPHA_DECODE (e->alpha));

	if (r_lightmap->value)
		d3d_Device->PSSetShaderResources (TEXTURE_SLOT_SPRITES, 1, &d3d_WhiteTexture.SRV);
	else d3d_Device->PSSetShaderResources (TEXTURE_SLOT_SPRITES, 1, &frame->Texture->SRV);

	d3d_Device->Draw (4, frame->FirstVertex);
	c_draw_call++;
}


void R_BeginSprites (void)
{
	d3d_Device->IASetPrimitiveTopology (D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
	d3d_SpriteShader.Bind ();
}


void R_EndSprites (void)
{
}


void R_AddSpriteToAlphaList (entity_t *e)
{
	R_AddAlphaObject (
		e,
		e->origin,
		R_BeginSprites,
		(alphaobjectdrawfunc_t) R_DrawSpriteModel,
		R_EndSprites
	);
}

